ES6 类和模块化
ES6 Class 类
在ES6中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。因为 class 的本质是 function 所以可以将其看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
//定义一个类
class User{
name = ''
constructor(name) {
this.name = name;
}
hello(){
console.log("hello " + this.name + " and study JavaScript oop!");
}
}
// 继承
class Student extends User{
grade = 0;
constructor(name,grade) {
super(name);
this.grade = grade;
}
study(){
console.log(this.name + " Say: I am grade "+ this.grade + " and I like Learning~");
}
}
//使用这个类
let alsritter = new Student('alsritter',2);
alsritter.hello();
alsritter.study();
传统定义类的三种方法
参考资料 Javascript定义类(class)的三种方法(好老啊)
1、构造函数法:它用构造函数模拟"类",在其内部用 this
关键字指代实例对象
function Cat() {
this.name = "大毛";
}
生成实例的时候,使用 new
关键字。
var cat1 = new Cat();
alert(cat1.name); // 大毛
类的属性和方法,还可以定义在构造函数的 prototype
对象之上。
Cat.prototype.makeSound = function(){
alert("喵喵喵");
}
2、Object.create()
:用这个方法,"类"就是一个对象,不是函数。(ES5 的语法)
var Cat = {
name: "大毛",
makeSound: function(){
alert("喵喵喵");
}
};
然后,直接用 Object.create()
生成实例,不需要用到 new
。
var cat1 = Object.create(Cat);
alert(cat1.name); // 大毛
cat1.makeSound(); // 喵喵喵
3、使用闭包的方式:这种方法不使用 this
和 prototype
,代码部署起来非常简单,它也是用一个对象模拟"类"。在这个类里面,定义一个构造函数 createNew()
,用来生成实例。
var Cat = {
createNew: function(){
var cat = {};
cat.name = "大毛";
cat.makeSound = function(){ alert("喵喵喵"); };
return cat;
}
};
// 使用的时候,调用 createNew() 方法,就可以得到实例对象。
var cat1 = Cat.createNew();
cat1.makeSound(); // 喵喵喵
基础用法
类表达式可以为匿名或命名。
// 匿名类
let Example = class {
constructor(a) {
this.a = a;
}
}
// 命名类
let Example = class Example {
constructor(a) {
this.a = a;
}
}
// 类声明
class Example {
constructor(a) {
this.a = a;
}
}
注意要点 类定义不会被提升,这意味着,必须在访问前对类进行定义,否则就会报错。
类中方法不需要 function
关键字。
类的属性
ES6 中,prototype
仍旧存在,虽然可以直接自类中定义方法,但是其实方法还是定义在 prototype
上的。 覆盖方法 / 初始化时添加方法
Example.prototype={
//methods
}
// 添加方法
Object.assign(Example.prototype,{
//methods
})
静态属性:class 本身的属性,即直接定义在类内部的属性( Class.propname
),不需要实例化。 ES6 中规定,Class 内部只有静态方法,没有静态属性。
class Example {
// 新提案
static a = 2;
}
// 目前可行写法
Example.b = 2;
公共属性
class Example{}
Example.prototype.a = 2;
实例属性
class Example {
a = 2;
constructor () {
console.log(this.a);
}
}
name 属性,返回跟在 class 后的类名(存在时)。
let Example = class Exam {
constructor(a) {
this.a = a;
}
}
console.log(Example.name); // Exam
let Example=class {
constructor(a) {
this.a = a;
}
}
console.log(Example.name); // Example
类的方法
constructor
方法是类的默认方法,创建类的实例化对象时被调用。
class Example{
constructor(){
console.log('我是 constructor');
}
}
new Example(); // 我是 constructor
在 ES2015/ES6 中引入了 class 关键字,只是语法糖,JavaScript 仍然是基于原型的,所以实际上这里定义的一个 class 也只是一个 “函数” 而已,例子如下
class Test {
constructor(){
// 默认返回实例对象 this
}
}
console.log(new Test() instanceof Test); // true
class Example {
constructor(){
// 指定返回对象
return new Test();
}
}
console.log(new Example() instanceof Example); // false
静态方法
class Example{
static sum(a, b) {
console.log(a+b);
}
}
Example.sum(1, 2); // 3
原型方法
class Example {
sum(a, b) {
console.log(a + b);
}
}
let exam = new Example();
exam.sum(1, 2); // 3
实例方法
class Example {
constructor() {
this.sum = (a, b) => {
console.log(a + b);
}
}
}
类的实例化
class 的实例化必须通过 new
关键字。(否则会报错)
class Example {
constructor(age) {
this.age = age;
}
}
let exam1 = new Example(18);
console.log(exam1); // Example { age: 18 }
这里的 new
关键字的原理
let exam1 = new Example(18);
JS 引擎在执行这句代码时,等价于如下的操作
function Example02(age) {
this.age = age;
}
var exam2 = {};
exam2.__proto__ = Example02.prototype;
// call 方法看上面
var result = Example02.call(exam2, 18);
console.log(exam2);
所以 exam2 的原型链是:exam2 -> Example02.prototype -> Object.prototype -> null
实例化对象
注意:它们是共享原型对象的,所以当原型改变了,这里被实例化的对象也改变了
class Example {
constructor(a, b) {
this.a = a;
this.b = b;
console.log('Example');
}
sum() {
return this.a + this.b;
}
}
let exam1 = new Example(2, 1);
let exam2 = new Example(3, 1);
console.log(exam1._proto_ == exam2._proto_); // true
exam1._proto_.sub = function() {
return this.a - this.b;
}
console.log(exam1.sub()); // 1
console.log(exam2.sub()); // 2
封装类属性
getter / setter(getter 与 setter 必须同级出现,不能单独出现)
class Example{
constructor(a, b) {
this.a = a; // 实例化时调用 set 方法
this.b = b;
}
get a(){
console.log('getter');
return this.a;
}
set a(a){
console.log('setter');
this.a = a; // 自身递归调用
}
}
let exam = new Example(1,2); // 不断输出 setter ,最终导致 RangeError
class Example1{
constructor(a, b) {
this.a = a;
this.b = b;
}
get a(){
console.log('getter');
return this._a;
}
set a(a){
console.log('setter');
this._a = a;
}
}
let exam1 = new Example1(1,2); // 只输出 setter , 不会调用 getter 方法
console.log(exam._a); // 1, 可以直接访问
extends 继承
使用 extends
关键字可以继承父类(其实还是原型链)
class Child extends Father { ... }
子类 constructor
方法中必须有 super
,且必须出现在 this
之前。
class Father {
constructor() {
console.log('hello this your father');
}
}
class Child extends Father {
constructor(a) {
super();
this.a = a;
console.log(`hello this ${this.a}`);
}
}
let test = new Child(13);
// hello this your father
// hello this 13
同 Java 一样,super
就是指向其父类的指针
class Father {
test() {
return 'I am your father';
}
static test1() {
return 1;
}
}
class Child extends Father {
constructor() {
super();
}
test2() {
console.log(`this my father's return: ${super.test()}`);
}
}
let obj = new Child();
obj.test2(); // this my father's return: I am your father
也可以在静态方法中使用 super
,指向其父类的静态方法
class Father {
static test1() {
return 1;
}
}
class Child2 extends Father {
constructor(){
super();
}
static test3(){
// 调用父类静态方法
return super.test1() + 2;
}
}
console.log(Child2.test3()); // 3
ES6 模块化
注意:这里是单独使用 babel 来进行打包,所以不依赖于 webpack
单独安装 babel
参考资料 babel 中文文档 参考资料 你真的会用 Babel 吗?
因为 NodeJS 无法直接使用 ES6 的模块化语法,所以需要使用 babel 来转成 CommonJS 规范
使用 npm 安装
# babel 不能直接使用 npm install babel 安装,因为 babel6 之后拆分成了几个核心包
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
# 虽然这种装在测试环境的工具一般可以全局安装,但是为了不隐含依赖于正在使用的全局环境,使项目更具可移植性,一般还是本地安装。
# 再额外安装一个 polyfill
# (这个是为了解决一些不转码的全局 api的问题,具体参考 https://juejin.im/post/6844904063402770439)
npm install --save @babel/polyfill
因为 babel6 之后拆分成了几个核心包,所以下面进行分别介绍
@babel/core
babel 的核心 api 都在这里面
@babel/cli
自带了一个内置的 CLI 命令行工具,可通过命令行编译文件(核心文件)
@babel/preset-env
可以根据配置的目标浏览器或者运行环境来自动将ES2015+的代码转换为es5
创建配置文件
项目根目录创建文件 babel.config.js
const presets = [
[
"@babel/env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
},
],
];
module.exports = { presets };
通过 npx
就可以直接在 NodeJS 里运行了
npx babel-node index.js
导入导出模块
// 导入模块不需要加 .js
import http from 'http'
import {default as http} from 'http'
import * as http from 'http'
import {get} from 'http'
// as 关键字和 sql 的那个 as 是一样的,就是别名
import {getList as get} from 'http'
import http, {getList} from 'fs'
export default http
export const http
export function getList
export {getList, get}
export * from 'http'
默认导出
参考资料 MDN--export 参考资料 开车去环游世界--ES6:export default 和 export 区别
export default 和 export 的区别
export
与 export default
均可用于导出常量、函数、文件、模块等
可以在其它文件或模块中通过 import+(常量 | 函数 | 文件 | 模块)
名的方式,将其导入,以便能够对其进行使用
在一个文件或模块中,export
、import
可以有多个,export default
仅有一个
通过 export
方式导出,在导入时要加 { }
,export default
则不需要
// 1.export
//a.js
export const str = "blablabla~";
export function log(sth) {
return sth;
}
// 对应的导入方式:
//b.js
import { str, log } from 'a'; //也可以分开写两次,导入的时候带花括号
// 2.export default
//a.js
const str = "blablabla~";
export default str;
// 对应的导入方式:
//b.js
import str from 'a'; //导入的时候没有花括号
// 如果默认导出和按需导出同时存在
//a.js
export default {a, b, c}
export let s1 = 'aaa'
export let s2 = 'bbb'
export function say(){
console.log('hello ES6')
}
// 对应的导入方式:
//b.js 如果按需导入想自定义名字则使用 as 关键字
import a, {s1, s2 as ss2, say} from 'a'
直接导入并执行模板代码
如果想要单纯的执行某个模块中的代码,并不需要得到模块中向外暴露的成员,此时可以直接导入并执行模块代码
// 文件模块 m2.js
// 在当前模块中执行一个 for 循环
for(let i = 0; i < 3; i++){
console.log(i);
}
// 直接导入并执行代码
import 'm2'